﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JeuDeDames_v1._02
{
    [Serializable]
    public class Ordinateur : Joueur
    {
        #region Propriétés
        public int profondeur = 2;
        private Coup meilleurCoup;
        private int nbDamesDepart;
        private int nbDamesAdverseDepart;
        #endregion

        #region Constructeurs
        public Ordinateur(int color)
        {
            couleur = color;
            this.lesPieces = new List<Piece>();
            this.piecesMangees = new List<int>();
            this.nbDames = 0;
            if (color == BLANC)
            {
                this.nom = "IA Blanc";
                this.monTour = true;
                indicePromote = new int[5] { 1, 3, 5, 7, 9 };
            }
            else if (color == NOIR)
            {
                this.nom = "IA Noir";
                this.monTour = false;
                indicePromote = new int[5] { 90, 92, 94, 96, 98 };
            }
        }

        public Ordinateur(int color, int profondeur, string nom)
        {
            couleur = color;
            this.profondeur = profondeur;
            this.nom = nom;
            this.lesPieces = new List<Piece>();
            this.piecesMangees = new List<int>();
            this.nbDames = 0;
            if (color == BLANC)
            {
                this.monTour = true;
                indicePromote = new int[5] { 1, 3, 5, 7, 9 };
            }
            else if (color == NOIR)
            {
                this.monTour = false;
                indicePromote = new int[5] { 90, 92, 94, 96, 98 };
            }
        } 
        #endregion

        #region Méthodes
        public Coup getMeilleurCoup()
        {
            return this.meilleurCoup;
        }
        #region Evaluation
        public int evaluation(Damier d, Joueur j)
        {
            int pDame = 3;
            int evaluation = 0;
            /** on détermine la couleur du joueur.
             *  evaluation est la différence de pieces entre le joueur et son adversaire.
             *  Une dame = 4 pions. donc on ajoute 3x nombre de dames.
             */
            if(j.couleur == BLANC)
            {
                evaluation = ((d.blanc.nbPieces + pDame * d.blanc.nbDames) - (d.noir.nbPieces + pDame * d.noir.nbDames)) + 4 * (d.blanc.nbDames - nbDamesDepart) - 5 * (d.noir.nbDames - nbDamesAdverseDepart); 
            }
            else if (j.couleur == NOIR)
            {
                evaluation = ((d.noir.nbPieces + pDame * d.noir.nbDames) - (d.blanc.nbPieces + pDame * d.blanc.nbDames)) + 4 * (d.noir.nbDames - nbDamesDepart) - 5 * (d.blanc.nbDames - nbDamesAdverseDepart);
            }
            
            //Si l'ordinateur gagne la partie on retourne +infini-nbtour;
            if (d.finPartie() == this.couleur)
            {
                return 1000000 - d.nbTour;
            }
            //Si l'ordinateur perd la partie on retourne -infini-nbtour;
            else if (d.finPartie() == -this.couleur)
            {
                return -1000000 - d.nbTour;
            }
            //Si il y a match nul.
            else if (d.finPartie() == 2)
            {
                //Si l'évaluation est à l'avantage du joueur, l'IA ne dois pas jouer le match nul. Sinon il joue le match nul.
                return -evaluation;
            }
            //Si il n'y a pas de fin de partie.
            else
            {
                return evaluation;
            }
        }
        #endregion

        #region MIN MAX
        public Coup minMaxPremier(Damier d)  // min correspond au minimum, max correspond au maximum.
        {
            nbDamesDepart = nbDames;
            if (coupsPossibles.Count() == 1)    //Si l'ordinateur a qu'un seul coup à jouer.
                return coupsPossibles[0];

            Coup meilleurCoup = null;
            Joueur adv = null;
            if (this == d.blanc)
                adv = d.noir;
            else if (this == d.noir)
                adv = d.blanc;
            int max = int.MinValue;
            int eval;
            Coup reference = null;
            List<Coup> coupsJouables = new List<Coup>();
            foreach (Coup c in this.coupsPossibles)
            {
                reference = c;
                this.jouer(d, ref reference);
                this.viderPiecesMangees(d);
                d.changerTour();    //change monTour du joueur et calcul ses coups possibles
                eval = MIN(d, adv, profondeur - 1);
                if (eval > max)
                {
                    max = eval;
                    coupsJouables.Clear();
                    coupsJouables.Add(c);
                }
                else if (eval == max)
                {
                    coupsJouables.Add(c);
                }
                d.annulerUnTour();
            }
            Random r = new Random();
            meilleurCoup = coupsJouables[r.Next(coupsJouables.Count())];
            return meilleurCoup;
        }

        private int MIN(Damier d, Joueur adv, int prof)
        {
            Joueur j = null;
            if (adv == d.blanc)
                j = d.noir;
            else if (adv == d.noir)
                j = d.blanc;

            if (prof == 0 || d.finPartie() != 0)
                return evaluation(d, j);
            else
            {
                int min = int.MaxValue;
                int eval;
                Coup reference;
                foreach (Coup c in adv.coupsPossibles)
                {
                    reference = c;
                    if (adv is Humain)
                        (adv as Humain).jouerIA(d, ref reference);
                    else
                        adv.jouer(d, ref reference);
                    adv.viderPiecesMangees(d);
                    d.changerTour();    //change monTour du joueur et calcul ses coups possibles
                    eval = MAX(d, j, prof - 1);
                    d.annulerUnTour();
                    if (eval < min)
                    {
                        min = eval;
                    }
                }
                return min;
            }
        }

        private int MAX(Damier d, Joueur j, int prof)
        {
            Joueur adv = null;
            if (j == d.blanc)
                adv = d.noir;
            else if (j == d.noir)
                adv = d.blanc;

            if (prof == 0 || d.finPartie() != 0)
                return evaluation(d, j);
            else
            {
                int max = int.MinValue;
                int eval;
                Coup reference;
                foreach (Coup c in j.coupsPossibles)
                {
                    reference = c;
                    j.jouer(d, ref reference);
                    j.viderPiecesMangees(d);
                    d.changerTour();
                    eval = MIN(d, adv, prof - 1);
                    d.annulerUnTour();
                    if (eval > max)
                    {
                        max = eval;
                    }
                }
                return max;
            }
        }
        #endregion

        #region ALPHA BETA
        #region Sans Multithread
        public Coup alphaBetaPremier(Damier d)  // min correspond au minimum, max correspond au maximum.
        {
            nbDamesDepart = nbDames;
            if (coupsPossibles.Count() == 1)
            {    //Si l'ordinateur a qu'un seul coup à jouer.
                return coupsPossibles[0];
            }

            /*joueur adverse*/
            Joueur adv = null;
            if (this == d.blanc)
            {
                adv = d.noir;
            }
            else if (this == d.noir)
            {
                adv = d.blanc;
            }

            /*Variables locales*/
            Coup meilleurCoup = null;
            int eval;
            Coup reference = null;
            List<Coup> coupsJouables = new List<Coup>();

            int alpha = int.MinValue;   //correspond à la borne inférieur de l'intervalle des notes évalués.
            int beta = int.MaxValue;    //correspond à la borne supérieur de l'intervalle des notes évalués.

            /*Pour tous les coups du joueur*/
            foreach (Coup c in this.coupsPossibles)
            {
                reference = c;
                this.jouer(d, ref reference);
                this.viderPiecesMangees(d);
                d.changerTour();    //change monTour du joueur et calcul ses coups possibles
                eval = ALPHA(d, adv, profondeur - 1, alpha, beta);
                d.annulerUnTour();
                
                //Si l'évaluation est égale à la meilleure évaluation. On ajoute le coup dans la liste.
                if (eval == alpha)
                {
                    coupsJouables.Add(c);
                }
                //Si l'évaluation est plus grande que la meilleure évaluation. On supprime les éléments de la liste et on ajoute le coup.
                else if (eval > alpha)
                {
                    alpha = eval;
                    coupsJouables.Clear();
                    coupsJouables.Add(c);
                }
            }
            Random r = new Random();
            meilleurCoup = coupsJouables[r.Next(coupsJouables.Count())];
            return meilleurCoup;
        }
        #endregion

        #region Avec Multithread
        public void alphaBetaPremier(Object d)  // min correspond au minimum, max correspond au maximum.
        {
            nbDamesDepart = nbDames;
            /*joueur adverse*/
            Joueur adv = null;
            if (this == ((Damier)d).blanc)
            {
                adv = ((Damier)d).noir;
            }
            else if (this == ((Damier)d).noir)
            {
                adv = ((Damier)d).blanc;
            }

            /*Variables locales*/
            Coup meilleurCoup = null;
            Coup reference = null;
            List<Coup> coupsJouables = new List<Coup>();

            int alpha = int.MinValue;   //correspond à la borne inférieur de l'intervalle des notes évalués.
            int beta = int.MaxValue;    //correspond à la borne supérieur de l'intervalle des notes évalués.

            /*Pour tous les coups du joueur*/
            foreach (Coup c in this.coupsPossibles)
            {
                reference = c;
                this.jouer(((Damier)d), ref reference);
                this.viderPiecesMangees(((Damier)d));
                ((Damier)d).changerTour();    //change monTour du joueur et calcul ses coups possibles
                reference.evaluation = ALPHA(((Damier)d), adv, profondeur - 1, alpha, beta);
                ((Damier)d).annulerUnTour();

                if (reference.evaluation == alpha)
                {//Si l'eval est égale à alpha.
                    coupsJouables.Add(c);
                }

                else if (reference.evaluation > alpha)
                {
                    alpha = reference.evaluation;
                    coupsJouables.Clear();
                    coupsJouables.Add(c);
                }
            }
            Random r = new Random();
            meilleurCoup = coupsJouables[r.Next(coupsJouables.Count())];
            this.meilleurCoup = meilleurCoup;
            this.meilleurCoup.evaluation = alpha;
        }
        #endregion

        private int ALPHA(Damier d, Joueur adv, int prof, int alpha, int beta)   //correspond au min
        {
            Joueur j = null;
            if (adv == d.blanc)
                j = d.noir;
            else if (adv == d.noir)
                j = d.blanc;

            if (prof == 0 || d.finPartie() != 0)
            {
                return evaluation(d, j);
            }
            else
            {
                //int alpha = int.MaxValue;
                int eval;
                Coup reference;
                foreach (Coup c in adv.coupsPossibles)
                {
                    reference = c;
                    if (adv is Humain)
                        (adv as Humain).jouerIA(d, ref reference);
                    else
                        adv.jouer(d, ref reference);
                    adv.viderPiecesMangees(d);
                    d.changerTour();    //change monTour du joueur et calcul ses coups possibles
                    eval = BETA(d, j, prof - 1, alpha, beta);
                    d.annulerUnTour();

                    /*Si l'eval est plus petite que evalMax, ça signifie que le coup de départ peut donner une situation moins avantageuse, donc on sort.*/
                    if (eval <= alpha)
                    {
                        return eval;
                    }
                    if (eval < beta)
                    {
                        beta = eval;
                    }
                }
                return beta;
            }
        }

        private int BETA(Damier d, Joueur j, int prof, int alpha, int beta)  //correspond au max
        {
            Joueur adv = null;
            if (j == d.blanc)
                adv = d.noir;
            else if (j == d.noir)
                adv = d.blanc;

            if (prof == 0 || d.finPartie() != 0)
                return evaluation(d, j);
            else
            {
                //int beta = int.MinValue;
                int eval;
                Coup reference;
                foreach (Coup c in j.coupsPossibles)
                {
                    reference = c;
                    j.jouer(d, ref reference);
                    j.viderPiecesMangees(d);
                    d.changerTour();
                    eval = ALPHA(d, adv, prof - 1, alpha, beta);
                    d.annulerUnTour();

                    if (eval >= beta)
                    {
                        return eval;
                    }
                    else
                    {
                        alpha = Math.Max(alpha, eval);
                    }
                }
                return alpha;
            }
        }
        #endregion

        #region Aléatoire
        public Coup coupAleatoire()
        {
            Random r = new Random();
            int n = r.Next(this.coupsPossibles.Count());
            return this.coupsPossibles[n];
        }
        #endregion
        #endregion
    }
}
